/*
 * Copyright (c) 2018 Hitevision
 */

package tw.com.hitevision.whiteboard.android.navigator;

import android.content.Context;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy;
import com.bumptech.glide.signature.ObjectKey;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import androidx.annotation.NonNull;
import androidx.viewpager.widget.PagerAdapter;
import tw.com.hitevision.whiteboard.android.R;
import tw.com.hitevision.whiteboard.android.state.WhiteboardState;
import tw.com.hitevision.whiteboard.android.storage.FileHelper;
import tw.com.hitevision.whiteboard.android.util.GlideApp;

public class PreviewPagerAdapter extends PagerAdapter {
    private static final String TAG = PreviewPagerAdapter.class.getSimpleName();

    private static final long MISSING_FILE_DELAY = 2000; // ms

    private final int itemsPerPage;

    private final Context context;
    private List<Item> items;
    private int selectedCount;
    private Listener listener;
    private boolean isMultiSelectMode;

    private SparseArray<PageHolder> pages;

    private int currentIndex;

    private boolean isAnnotationMode;

    public PreviewPagerAdapter(Context context) {
        this.context = context;

        items = new ArrayList<>();
        pages = new SparseArray<>();

        itemsPerPage = context.getResources().getInteger(R.integer.nagivator_max_items_per_page);
    }


    public void setStates(List<WhiteboardState> states, int currentIndex) {
        isAnnotationMode = false;
        this.currentIndex = currentIndex;
        items.clear();

        for (WhiteboardState state : states) {
            items.add(new Item(state));
        }

        selectedCount = 0;
        isMultiSelectMode = false;

        for (int i = 0; i < pages.size(); i++) {
            final PageHolder holder = pages.valueAt(i);
            if (holder != null) {
                fillPage(holder, pages.keyAt(i));
            }
        }

        if (getCount() != pages.size()) {
            notifyDataSetChanged();
        }
    }

    public void setFiles(List<File> files) {
        isAnnotationMode = true;
        currentIndex = 0;
        items.clear();

        for (File file : files) {
            items.add(new Item(file));
        }

        selectedCount = 0;
        isMultiSelectMode = true;

        for (int i = 0; i < pages.size(); i++) {
            final PageHolder holder = pages.valueAt(i);
            if (holder != null) {
                fillPage(holder, pages.keyAt(i));
            }
        }

        if (getCount() != pages.size()) {
            notifyDataSetChanged();
        }
    }

    public boolean isEmpty() {
        return items.isEmpty();
    }

    @Override
    public int getCount() {
        return items.isEmpty() ? 1 : (items.size() - 1) / itemsPerPage + 1;
    }

    @NonNull
    @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        final ViewGroup page = (ViewGroup) LayoutInflater.from(container.getContext())
                .inflate(R.layout.navigator_page, container, false);
        page.setTag(position);

        final PageHolder holder = new PageHolder(page);
        pages.append(position, holder);

        fillPage(holder, position);

        container.addView(page);
        return holder;
    }

    private class PageHolder {
        private ViewGroup page;
        private ItemHolder[] childs;

        PageHolder(ViewGroup page) {
            this.page = page;
            childs = new ItemHolder[itemsPerPage];

            for (int i = 0; i < itemsPerPage; i++) {
                childs[i] = new ItemHolder(page.getChildAt(i));
            }
        }
    }

    private class ItemHolder {
        private View itemView;
        private ImageView preview;
        private TextView number;
        private View check;

        ItemHolder(View view) {
            itemView = view;
            preview = view.findViewById(R.id.preview);
            number = view.findViewById(R.id.number);
            check = view.findViewById(R.id.check);

            itemView.setOnClickListener(v -> {
                final Item item = (Item) v.getTag();
                if (item != null) {
                    if (isMultiSelectMode) {
                        final boolean isSelected = !item.isSelected();

                        v.setSelected(isSelected);
                        check.setVisibility(isSelected ? View.VISIBLE : View.GONE);
                        item.setSelected(isSelected);
                        if (isSelected) {
                            selectedCount++;
                        } else {
                            selectedCount--;
                        }

                        // disable multi-select mode when no items are selected
                        if (selectedCount == 0 && !isAnnotationMode) {
                            isMultiSelectMode = false;
                        }

                        if (listener != null) {
                            listener.onSelectedItemsChanged(selectedCount, items.size());
                        }
                    } else {
                        if (listener != null) {
                            listener.onStateClick(item.getStateId());
                        }
                    }
                }
            });

            itemView.setOnLongClickListener(v -> {
                final Item item = (Item) v.getTag();
                if (item != null) {
                    isMultiSelectMode = true;

                    final boolean isSelected = !item.isSelected();

                    v.setSelected(isSelected);
                    check.setVisibility(isSelected ? View.VISIBLE : View.GONE);

                    item.setSelected(isSelected);
                    if (isSelected) {
                        selectedCount++;
                    } else {
                        selectedCount--;
                    }

                    if (listener != null) {
                        listener.onSelectedItemsChanged(selectedCount, items.size());
                    }
                }
                return true;
            });
        }
    }

    private void fillPage(PageHolder page, int position) {
        final int start = position * itemsPerPage;
        final int end = start + itemsPerPage;

        int index = start;
        while (index < end && index < items.size()) {
            ItemHolder child = page.childs[index - start];
            setView(child, index);
            index++;
        }

        // disable rest of previews
        if (index >= items.size()) {
            while (index < end) {
                ItemHolder child = page.childs[index - start];
                disableView(child);
                index++;
            }
        }
    }

    private void setView(ItemHolder holder, int index) {
        if (!holder.itemView.isEnabled()) {
            enableView(holder);
        }

        final Item item = items.get(index);
        final File thumbnail = isAnnotationMode
                ? FileHelper.getInstance(context).getPreviewFile(item.getPreviewFile())
                : FileHelper.getInstance(context).getThumbnailFile(item.getPreviewFile());

        if (thumbnail.exists()) {
            GlideApp.with(context.getApplicationContext())
                    .load(thumbnail.getPath())
                    .downsample(DownsampleStrategy.CENTER_INSIDE)
                    .signature(new ObjectKey(thumbnail.lastModified()))
                    .placeholder(R.drawable.preview_placeholder)
                    .centerInside()
                    .into(holder.preview);

            if (index == currentIndex) {
                holder.itemView.postDelayed(() -> {
                    if (thumbnail.exists()) {
                        GlideApp.with(context.getApplicationContext())
                                .load(thumbnail.getPath())
                                .downsample(DownsampleStrategy.CENTER_INSIDE)
                                .signature(new ObjectKey(thumbnail.lastModified()))
                                .centerInside()
                                .into(holder.preview);
                    }
                }, MISSING_FILE_DELAY);
            }
        } else {
            holder.itemView.postDelayed(() -> {
                if (thumbnail.exists()) {
                    GlideApp.with(context.getApplicationContext())
                            .load(thumbnail.getPath())
                            .downsample(DownsampleStrategy.CENTER_INSIDE)
                            .signature(new ObjectKey(thumbnail.lastModified()))
                            .placeholder(R.drawable.preview_placeholder)
                            .centerInside()
                            .into(holder.preview);
                }
            }, MISSING_FILE_DELAY);
        }

        holder.number.setText(Integer.toString(index + 1));
        holder.check.setVisibility(item.isSelected() ? View.VISIBLE : View.GONE);
        holder.itemView.setSelected(item.isSelected());
        holder.itemView.setTag(item);
    }

    private void enableView(ItemHolder holder) {
        holder.itemView.setEnabled(true);
        holder.preview.setImageBitmap(null);
        holder.preview.setVisibility(View.VISIBLE);
        holder.number.setVisibility(View.VISIBLE);
        holder.check.setVisibility(View.VISIBLE);
    }

    private void disableView(ItemHolder holder) {
        GlideApp.with(context.getApplicationContext())
                .clear(holder.preview);
        holder.preview.setImageResource(0);

        holder.itemView.setEnabled(false);
        holder.itemView.setTag(null);

        holder.preview.setVisibility(View.GONE);
        holder.number.setVisibility(View.GONE);
        holder.check.setVisibility(View.GONE);
    }

    @Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        final PageHolder page = pages.get(position);
        pages.remove(position);
        container.removeView(page.page);
    }

    @Override
    public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
        return view == ((PageHolder) object).page;
    }

    public void clear() {
        for (int i = 0; i < pages.size(); i++) {
            final PageHolder page = pages.valueAt(i);
            if (page != null) {
                for (int j = 0; j < page.childs.length; j++) {
                    final ItemHolder child = page.childs[j];
                    if (child != null) {
                        disableView(child);
                    }
                }
            }
        }

        items.clear();

        if (!isAnnotationMode) {
            isMultiSelectMode = false;
        }
        selectedCount = 0;
        notifyDataSetChanged();
    }

    /**
     * Clears selected state for all items.
     */
    public void clearSelected() {
        for (int i = 0, size = items.size(); i < size; i++) {
            final Item item = items.get(i);
            if (item.isSelected()) {
                item.setSelected(false);
                updateView(i);
            }
        }

        if (!isAnnotationMode) {
            isMultiSelectMode = false;
        }
        selectedCount = 0;
    }

    private void updateView(int itemIndex) {
        final int pageIndex = itemIndex / itemsPerPage;
        PageHolder page = pages.get(pageIndex);

        if (page != null) {
            final int viewIndex = itemIndex % itemsPerPage;
            ItemHolder holder = page.childs[viewIndex];

            setView(holder, itemIndex);
        }
    }

    /**
     * Sets all items as selected.
     */
    public void selectAll() {
        for (int i = 0, size = items.size(); i < size; i++) {
            final Item item = items.get(i);
            if (!item.isSelected()) {
                item.setSelected(true);
                updateView(i);
            }
        }

        isMultiSelectMode = true;
        selectedCount = items.size();
    }

    public List<Long> getSelectedIds() {
        final List<Long> ids = new ArrayList<>();

        for (Item item : items) {
            if (item.isSelected()) {
                ids.add(item.getStateId());
            }
        }

        return ids;
    }

    public List<Item> getSelected() {
        final List<Item> selected = new ArrayList<>();

        for (Item item : items) {
            if (item.isSelected()) {
                selected.add(item);
            }
        }

        return selected;
    }

    public int getItemsSize() {
        return items.size();
    }

    /**
     * Deletes selected items.
     *
     * @return a list of state ids of deleted items
     */
    public List<Long> deleteSelected() {
        final int oldPageCount = getCount();
        final List<Long> deleted = new ArrayList<>();

        int firstDeleted = items.size();
        for (int i = items.size() - 1; i >= 0; i--) {
            Item item = items.get(i);
            if (item.isSelected()) {
                firstDeleted = i;

                items.remove(i);
                deleted.add(item.getStateId());
            }
        }

        if (!items.isEmpty() && firstDeleted < items.size()) {
            for (int i = firstDeleted, size = items.size(); i < size; i++) {
                updateView(i);
            }
        }

        if (items.isEmpty()) {
            return deleted;
        }

        // disable rest of previews
        final int pagesCount = (items.size() - 1) / itemsPerPage + 1;

        final int totalViews = pagesCount * itemsPerPage;
        if (items.size() < totalViews) {
            final PageHolder page = pages.get(pagesCount - 1);
            if (page != null) {
                int index = items.size() % itemsPerPage;
                while (index < itemsPerPage) {
                    ItemHolder child = page.childs[index];
                    disableView(child);
                    index++;
                }
            }
        }

        if (pagesCount != oldPageCount) {
            notifyDataSetChanged();
        }

        isMultiSelectMode = false;
        selectedCount = 0;
        return deleted;
    }

    @Override
    public int getItemPosition(@NonNull Object object) {
        final int index = pages.indexOfValue((PageHolder) object);
        if (index < 0) {
            return POSITION_UNCHANGED;
        } else if (pages.keyAt(index) < getCount()) {
            return POSITION_UNCHANGED;
        } else {
            return POSITION_NONE;
        }
    }

    public void setListener(Listener listener) {
        this.listener = listener;
    }

    public interface Listener {
        void onStateClick(long stateId);

        void onSelectedItemsChanged(int selectedCount, int itemsSize);
    }

    private static void log(String msg) {
        Log.d(TAG, msg);
    }
}
